package com.usefullc.crawler.selenium;

import cn.hutool.core.util.ZipUtil;
import com.usefullc.common.enums.YesNoEnum;
import com.usefullc.common.util.http.HttpNewUtils;
import com.usefullc.common.util.http.ResponseRes;
import com.usefullc.crawler.constants.CrawlerConstants;
import com.usefullc.crawler.enums.WebDriverTypeEnum;
import com.usefullc.crawler.service.ActiveInstanceService;
import com.usefullc.crawler.service.ActiveProcessService;
import com.usefullc.system.domain.DataConfig;
import com.usefullc.system.service.DataConfigService;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.RandomUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.openqa.selenium.Cookie;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.firefox.FirefoxOptions;
import org.openqa.selenium.firefox.FirefoxProfile;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import sun.net.util.URLUtil;

import javax.annotation.PostConstruct;
import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

/**
 * @author: Sampson
 * @date: 2022-03-29 16:56
 */
@Service
public class CrawlerWebDriverBuilder3 {

    private static Logger logger = LoggerFactory.getLogger(CrawlerWebDriverBuilder3.class);

    @Autowired
    private DataConfigService dataConfigService;

    @Autowired
    private ActiveInstanceService activeInstanceService;

    @Autowired
    private ActiveProcessService activeProcessService;

    private CrawlerWebDriverParam crawlerWebDriverParam;


    private int maxWebDriverCount = 5;




    //    public  void setMaxWebDriverCount(int count){
//        maxWebDriverCount = count;
//    }

    private BlockingQueue<CrawlerWebDriver> webDriverBlockingQueue = new ArrayBlockingQueue<>(maxWebDriverCount);
//    private BlockingQueue<CrawlerWebDriver> webDriverBlockingWorkQueue = new ArrayBlockingQueue<>(maxWebDriverCount);
//    private  AtomicInteger newCount = new AtomicInteger();

    private List<String> proxyList = new ArrayList<>();

    private void initProxy() {
        try {
            proxyList.clear();

            //初始化代理
            List<String> strings = FileUtils.readLines(new File("/Users/long3/getips.txt"), "utf-8");
            for (String string : strings) {
                proxyList.add(string);
            }

//        proxyList.add("localhost:8888");
//        proxyList.add("localhost:8889");
        } catch (Exception e) {
            logger.error("error", e);

        }
    }

    @PostConstruct
    public void init() {
        try {
            //销毁webdriver
            destroy();

            //初始化代理
//            initProxy();

            //初始化参数
//            newCount.set(0);
            DataConfig dataConfig = dataConfigService.findByCode(CrawlerConstants.ACTIVE_TASK_INTERCEPT);
            activeProcessService.setAtoIntercept(BooleanUtils.toBoolean(dataConfig.getValue()));

            dataConfig = dataConfigService.findByCode(CrawlerConstants.WEB_DRIVER_MAX_COUNT);
            maxWebDriverCount = Integer.valueOf(dataConfig.getValue());
        } catch (Exception e) {
            logger.error("error", e);
        }

    }


    public CrawlerWebDriver buildFirefox(CrawlerWebDriverParam param) {
        System.setProperty("webdriver.firefox.logfile", "/dev/null");
        FirefoxProfile profile = new FirefoxProfile();
//        ProfilesIni allProfiles = new ProfilesIni();
//        FirefoxProfile profile = allProfiles.getProfile("NoJs");

//        profile.setPreference("javascript.enabled",false);
        FirefoxOptions options = new FirefoxOptions();
        options.setProfile(profile);
        options.addArguments("--start-maximized");
//        options.addArguments("--disable-javascript");
        options.addArguments("--disable-infobars");
        options.addArguments("--disable-logging");
//        options.addArguments("headless");
        if (param.getEnableImage() == YesNoEnum.YES.getCode()) {
            options.addPreference("permissions.default.image", 2);
        }
        if (param.getEnableJavascript() == YesNoEnum.YES.getCode()) {
            //TODO
        }
//        chromePrefs.put("profile.managed_default_content_settings.css",2);
        if (param.getEnableStyle() == YesNoEnum.YES.getCode()) {
            options.addPreference("permissions.default.stylesheet", 2);
        }
//        options.addPreference("dom.ipc.plugins.enabled.libflashplayer.so",false);
        options.addPreference("useAutomationExtension", false);
        options.addPreference("excludeSwitches", "enable-automation");
//            options.addPreference("excludeSwitches","--disable-logging");
//            options.addPreference("excludeSwitches"," enable-logging");
//            options.setExperimentalOption("excludeSwitches", Collections.singletonList("enable-automation"));
        FirefoxDriver driver = new FirefoxDriver(options);
        driver.manage().window().maximize();
//            driver.setLogLevel(Level.OFF);
        CrawlerWebDriver webDriver = new CrawlerWebDriver();
        webDriver.setAvailable(true);
        webDriver.setType(param.getWebDriverType());
        webDriver.setWork(true);
        String runRdMs = param.getTimeRdMs();
        if (StringUtils.isNotEmpty(runRdMs)) {
            String[] rds = runRdMs.split("-");
            webDriver.setRunMinMs(Integer.valueOf(rds[0]));
            webDriver.setRunMaxMs(Integer.valueOf(rds[1]));
        }
        webDriver.setWebDriver(driver);
        webDriver.setUse(false);

        return webDriver;
    }

    public CrawlerWebDriver buildChrome(CrawlerWebDriverParam param) {
        CrawlerWebDriver webDriver = new CrawlerWebDriver();
        ChromeOptions options = new ChromeOptions();
        //去掉chrome 正受到自动测试软件的控制
//            options.addArguments("disable-infobars");

        options.addArguments("--disable-infobars");
//        options.addArguments("--disable-extensions");
        // 允许重定向
//        options.addArguments("--disable-web-security");
        // 最大化
//        options.addArguments("--start-maximized");
//        options.addArguments("--no-sandbox");

        //
//        options.addArguments("--headless");
        options.addArguments("--disable-gpu");
        options.addArguments("--no-sandbox");
//        options.addArguments("--allow-RUNNING-insecure-content");  //设置proxy时添加的额外参数，允许不安全的证书
//        options.addArguments("--ignore-certificate-errors");  //设置proxy时添加的额外参数，允许不安全的证书
        options.addArguments("user-agent='Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.81 Safari/537.36'");

//            options.addArguments("--proxy-server=%s");
//            options.addArguments("--enable-automation");

//            options.setExperimentalOption("debuggerAddress","127.0.0.1:9222");

        //开启开发者模式
        options.setExperimentalOption("useAutomationExtension", false);
        options.setExperimentalOption("excludeSwitches", Collections.singletonList("enable-automation"));

        HashMap<String, Object> chromePrefs = new HashMap<>();
//        chromePrefs.put("permissions.default.stylesheet",2);
        if (param.getEnableImage() == YesNoEnum.YES.getCode()) {
            chromePrefs.put("profile.managed_default_content_settings.images", 2);
        }
        if (param.getEnableJavascript()  == YesNoEnum.YES.getCode()) {
            chromePrefs.put("profile.managed_default_content_settings.javascript", 2);
        }
//        chromePrefs.put("profile.managed_default_content_settings.css",2);
        if (param.getEnableStyle() == YesNoEnum.YES.getCode()) {
//            chromePrefs.put("profile.managed_default_content_settings.stylesheet", 2);
            //TODO
        }
//        chromePrefs.put("profile.managed_default_content_settings.permissions.default.stylesheet",2);
//        chromePrefs.put("profile.managed_default_content_settings.javascript",2);
        options.setExperimentalOption("prefs", chromePrefs);

//        WebDriver driver = new ChromeDriver(options);
//        driver.manage().window().maximize();

        DesiredCapabilities capabilities = DesiredCapabilities.chrome();

        //proxy 处理
        if (param.getEnableProxy() == YesNoEnum.YES.getCode()) {
//            Proxy proxy = new Proxy();
//            int index = newCount.get() % proxyList.size();
            //及时获取
            ResponseRes responseRes = HttpNewUtils.get("https://v1.api.juliangip.com/dynamic/getips?filter=1&num=1&pt=1&result_type=text&split=1&trade_no=1722113030411733&sign=86465808fdf101b911e0011543738ca1");
            String bodyText = responseRes.getBodyText();
            String proxyPort = bodyText.trim();
            while(!StringUtils.contains(proxyPort,":")){
                responseRes = HttpNewUtils.get("https://v1.api.juliangip.com/dynamic/getips?filter=1&num=1&pt=1&result_type=text&split=1&trade_no=1722113030411733&sign=86465808fdf101b911e0011543738ca1");
                bodyText = responseRes.getBodyText();
                proxyPort = bodyText.trim();
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                }
            }
            webDriver.setProxy(proxyPort);
//            proxy.setHttpProxy(proxyPort);
//            proxy.setSslProxy(proxyPort);

//            proxy.setSocksUsername("15267164682");
//            proxy.setSocksPassword("pvs70z4l");
//            capabilities.setCapability("proxy", proxy);

//            options.addArguments("--proxy-server=http://"+proxyPort);


//            ChromeOptions chromeOptions = new ChromeOptions();
            try {
                String basicDir = "/Users/long3/workspace/usefullc-platform/usefullc-crawler/src/main/resources/static/";
                String backgroundContent = FileUtils.readFileToString(new File(basicDir + "background.js.tpl"), "utf-8");
                backgroundContent = backgroundContent.replaceAll("YOU_PROXY_ADDRESS", proxyPort.split(":")[0]);
                backgroundContent = backgroundContent.replaceAll("YOUR_PROXY_PORT", proxyPort.split(":")[1]);
                backgroundContent = backgroundContent.replaceAll("YOUR_PROXY_USERNAME", "15267164682");
                backgroundContent = backgroundContent.replaceAll("YOUR_PROXY_PASSWORD", "pvs70z4l");
                FileUtils.writeStringToFile(new File(basicDir + "extension/background.js"), backgroundContent, "utf-8");

                ZipUtil.zip(basicDir + "extension", basicDir + "extension.zip", false);
//                ZipCompressUtils.zip(basicDir+"extension.zip", basicDir+"extension");
                File file = new File(basicDir + "extension.zip");
                options.addExtensions(file);
            } catch (Exception e) {
                logger.error("error", e);
            }
        }

//        ChromeOptions options = new ChromeOptions();
//        options.addArguments("start-maximized");

        capabilities.setCapability(ChromeOptions.CAPABILITY, options);

        WebDriver driver = new ChromeDriver(capabilities);
        driver.manage().window().maximize();

        //页面加载超时,注释掉，不然css,image 不加载，也会超时
//        driver.manage().timeouts().implicitlyWait(30, TimeUnit.SECONDS);



        webDriver.setAvailable(true);
        webDriver.setType(param.getWebDriverType());
        webDriver.setWork(true);
        String runRdMs = param.getTimeRdMs();
        if (StringUtils.isNotEmpty(runRdMs)) {
            String[] rds = runRdMs.split("-");
            webDriver.setRunMinMs(Integer.valueOf(rds[0]));
            webDriver.setRunMaxMs(Integer.valueOf(rds[1]));
        }
        webDriver.setWebDriver(driver);
        webDriver.setUse(false);
        return webDriver;
    }

    private void preExecute(WebDriver driver) {
        JavascriptExecutor driverJs = ((JavascriptExecutor) driver);
        driverJs.executeScript("Object.defineProperty(navigator, 'webdriver', {\n" +
                "      get: () => undefined\n" +
                "    })");
    }

    private void init(CrawlerWebDriverParam param, int num) {
        String type = param.getWebDriverType();
        for (int i = 0; i < num; i++) {
            CrawlerWebDriver webDriver = null;
            try{
                if (StringUtils.equalsIgnoreCase(type, WebDriverTypeEnum.FIREFOX.getCode())) {
                    webDriver = buildFirefox(param);
                } else {
                    webDriver = buildChrome(param);
//                webDriverBlockingQueue.add(webDriver);
                }
                webDriverBlockingQueue.add(webDriver);
                if(webDriverBlockingQueue.size() >= maxWebDriverCount){
                    break;
                }

            }catch (Exception e){
                //添加失败，直接关闭webdriver
                logger.error("error",e);
                if(webDriver != null && webDriver.getWebDriver() != null){
                    webDriver.getWebDriver().quit();
                }
                break;
            }
//            newCount.incrementAndGet();
//            webDriverBlockingFreeQueue.add(webDriver);
        }
    }


    public Object executeScript(WebDriver driver, String script) {
        try {
            JavascriptExecutor driverJs = ((JavascriptExecutor) driver);
            return driverJs.executeScript(script);
        } catch (Exception e) {
            logger.error("execute script failed! script=\n{}", script);
            throw e;
        }
    }

    private synchronized CrawlerWebDriver get() {
        try {
//            CrawlerWebDriver[] crawlerWebDrivers = webDriverBlockingQueue.toArray(new CrawlerWebDriver[]{});
            CrawlerWebDriver crawlerWebDriver = webDriverBlockingQueue.stream().filter(data -> data.isWork() && data.isAvailable()).findFirst().orElse(null);
            while(crawlerWebDriver == null) {
                Thread.sleep(100);
                crawlerWebDriver = webDriverBlockingQueue.stream().filter(data -> data.isWork() && data.isAvailable()).findFirst().orElse(null);
            }
//////            synchronized (webDriverBlockingQueue) {
//            CrawlerWebDriver crawlerWebDriver = webDriverBlockingQueue.take();
////            aliveCount.incrementAndGet();
////            WebDriver webDriver = crawlerWebDriver.getWebDriver();
//            while (!this.canUse(crawlerWebDriver)) {
//                crawlerWebDriver = webDriverBlockingQueue.take();
////                aliveCount.incrementAndGet();
//            }
//            webDriverBlockingWorkQueue.add(crawlerWebDriver);
            crawlerWebDriver.setAvailable(false);
            return crawlerWebDriver;
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    private boolean canUse(CrawlerWebDriver crawlerWebDriver) {
        WebDriver webDriver = crawlerWebDriver.getWebDriver();
        try {
            if (!crawlerWebDriver.isWork()) {
                return false;
            }
            webDriver.getCurrentUrl();
        } catch (Exception e) {
            logger.error("CrawlerWebDriver is not use ,object={}", crawlerWebDriver);
            return false;
        }
        return true;
    }


    public CrawlerWebDriver open(String url, CrawlerWebDriverParam param) {
        try {
            this.crawlerWebDriverParam = param;
            //已拦截，暂停调试处理
            if(param.getActiveInstanceId() != null){
                activeProcessService.checkIntercept(param.getActiveInstanceId());
            }

            CrawlerWebDriver crawlerWebDriver = get();
            WebDriver webDriver = crawlerWebDriver.getWebDriver();

            //处理打开url 频率
            long takeTime = System.currentTimeMillis() - crawlerWebDriver.getUseTime();
            if (takeTime < crawlerWebDriver.getRunMaxMs()) {
                long runMinMs = crawlerWebDriver.getRunMinMs();
                long min = NumberUtils.min(runMinMs, takeTime);
                long max = NumberUtils.max(runMinMs, takeTime);
                long rdNum = RandomUtils.nextLong(min, max);
                logger.info("sleep {} ms at {} ", rdNum, crawlerWebDriver.getNo());
                Thread.sleep(rdNum);
            }
            //end

            if (crawlerWebDriver.isUse()) {
                crawlerWebDriver.setUseTime(System.currentTimeMillis());
                webDriver.navigate().to(url);
            } else {
                crawlerWebDriver.setUse(true);
                crawlerWebDriver.setUseTime(System.currentTimeMillis());
                webDriver.get(url);
                if (StringUtils.equalsIgnoreCase(param.getWebDriverType(), WebDriverTypeEnum.CHROME.getCode())) {
                    preExecute(crawlerWebDriver.getWebDriver());
                }
            }
            //cookie
            String cookie = param.getCookie();
            if(StringUtils.isNotEmpty(cookie)){
                crawlerWebDriver.getWebDriver().manage().deleteAllCookies();
                String[] cookieArray = cookie.split(";");
                for (String cookieStr : cookieArray) {
                    String[] cookieNameValue = cookieStr.trim().split("=");
                    URL oUrl = new URL(url);
                    String domain = oUrl.getHost();
                    Cookie webDriverCookie = new Cookie(cookieNameValue[0],cookieNameValue[1],domain,"/",null);
                    try{
                        crawlerWebDriver.getWebDriver().manage().addCookie(webDriverCookie);
                    }catch (Exception e){
                       logger.info("set cookie error,cookieStr={}",cookieStr);
                       e.printStackTrace();
                    }
                }
            }
            return crawlerWebDriver;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public void scrollDown(WebDriver driver) {
        JavascriptExecutor driverJs = ((JavascriptExecutor) driver);
        try {
            for (int i = 0; i < 50; i++) {
                driverJs.executeScript("scrollTo(0," + (i * 100) + ")");
                Thread.sleep(10);
            }
        } catch (Exception e) {
        }
    }

    public  void close(CrawlerWebDriver crawlerWebDriver) {
        if (crawlerWebDriver != null) {
            crawlerWebDriver.setAvailable(true);
//            synchronized (crawlerWebDriver) {
//                boolean state = webDriverBlockingQueue.remove(crawlerWebDriver);
//                if (state) {
//                    init(crawlerWebDriverParam,1);
////                    if (!webDriverBlockingWorkQueue.contains(crawlerWebDriver)) {
////                        webDriverBlockingQueue.add(crawlerWebDriver);
////                    }
//                }
//            }
        }
    }


    public  void closeDirect(CrawlerWebDriver crawlerWebDriver) {
        if (crawlerWebDriver != null) {
            crawlerWebDriver.setAvailable(true);
            try{
                crawlerWebDriver.getWebDriver().quit();
            }catch (Exception e){
                logger.error("error quit failed",e);
            }
//            synchronized (crawlerWebDriver) {
//                boolean state = webDriverBlockingQueue.remove(crawlerWebDriver);
//                if (state) {
//                    init(crawlerWebDriverParam,1);
////                    if (!webDriverBlockingWorkQueue.contains(crawlerWebDriver)) {
////                        webDriverBlockingQueue.add(crawlerWebDriver);
////                    }
//                }
//            }
        }
    }

    /**
     * 干掉当前的，再生产一个新的，针对代理失效的情况
     * @param crawlerWebDriver
     */
    public  void killAndBuildNew(CrawlerWebDriver crawlerWebDriver) {
        if (crawlerWebDriver != null) {
//            synchronized (crawlerWebDriver) {
//                crawlerWebDriver.setAvailable(true);
                try{
                    crawlerWebDriver.getWebDriver().quit();
                }catch (Exception e){
                    logger.error("error quit failed",e);
                }
                boolean state = webDriverBlockingQueue.remove(crawlerWebDriver);
                if (state) {
                    init(crawlerWebDriverParam,1);
                }
//            }
        }
    }

    //todo 需要异步兼容webdriver 的状态，不然导致死锁
    private void destroy() {
        CrawlerWebDriver crawlerWebDriver = webDriverBlockingQueue.poll();
        while (crawlerWebDriver != null) {
            crawlerWebDriver.getWebDriver().quit();
            crawlerWebDriver = webDriverBlockingQueue.poll();

        }
//        CrawlerWebDriver crawlerWorkWebDriver = webDriverBlockingWorkQueue.poll();
//        while (crawlerWorkWebDriver != null) {
//            crawlerWorkWebDriver.getWebDriver().quit();
//            crawlerWorkWebDriver = webDriverBlockingWorkQueue.poll();
//        }
    }


    public void monitor() {
        if(crawlerWebDriverParam == null){
            return;
        }
        //
        if (!webDriverBlockingQueue.isEmpty()) {
            CrawlerWebDriver[] crawlerWebDrivers = webDriverBlockingQueue.toArray(new CrawlerWebDriver[]{});
            for (CrawlerWebDriver crawlerWebDriver : crawlerWebDrivers) {

                //客户端已死掉的
                boolean dead = false;
                if(!this.canUse(crawlerWebDriver)){
                    webDriverBlockingQueue.remove(crawlerWebDriver);
                    dead = true;
                }
                //客户端僵尸，没有及时回收到 webDriverBlockingQueue
                if (!dead && crawlerWebDriver.isUse() && !crawlerWebDriver.isAvailable()) {
                    int timeout = 1 * 60 * 1000;
                    if (System.currentTimeMillis() - crawlerWebDriver.getUseTime() > timeout) {
                        crawlerWebDriver.setAvailable(true);
                    }
                }
            }
        }
        int newCount = maxWebDriverCount - webDriverBlockingQueue.size();
        if (newCount > 0) {
            init(crawlerWebDriverParam, newCount);
        }
    }
}
